package com.hbwxz.volatileDemo;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * volatile 关键字，只保证线程可见性，但是不保证原子性
 * <p>
 * 示例中的 i++ 其实步骤为三步
 * ① i++不具备原子性，该操作是先读取值，然后写回一个新值，相当于原来的值加上1，分3步完成
 * ② 如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值，那么第二个线程就会与第一个线程一起看到同一个值，
 * ③ 并执行相同值的加1操作，这也就造成了线程安全失败，因此对于add方法必须使用synchronized修饰，以便保证线程安全.
 * <p>
 * 总结：由此可见 volatile 解决的是变量读时的可见性问题，但无法保证原子性，对于多线程修改共享变量的场景必须使用加锁同步
 *
 * @author shenzw
 * @date 2023/12/20
 */
public class VolatileDemo {

    public static void main(String[] args) throws InterruptedException {
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(50); // 解决方案引入 countDownLatch，等待所有子线程都执行完成后再去取值

        for (int i = 1; i <= 50; i++) { // 开启 10个线程，每个线程加到 1000，总计预期为：10000
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000; j++) {
                        myNumber.addPlusPlus();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }

        // 暂停几秒钟线程
//        try {
//            TimeUnit.SECONDS.sleep(3);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + myNumber.getNumber()); // main	9981；main	9818；main	9705；
    }
}

//class MyNumber {
//    volatile int number = 0;
//
//    public synchronized void addPlusPlus() { // 加 synchronized可以实现预期效果
//        number++;
//    }
//}

// AtomicInteger CAS（compare and swap），不需要加锁了
class MyNumber {
    //    volatile int number = 0;
    AtomicInteger atomicInteger = new AtomicInteger();

    public void addPlusPlus() {
        atomicInteger.getAndIncrement();
    }

    public int getNumber() {
        return atomicInteger.get();
    }
}